import { Logger, ValidationPipe, VersioningType } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { NestFactory } from "@nestjs/core";
import { NestExpressApplication } from "@nestjs/platform-express";
import { DocumentBuilder, SwaggerCustomOptions, SwaggerDocumentOptions, SwaggerModule } from "@nestjs/swagger";
import * as session from "express-session";
import helmet from "helmet";
import { WinstonModule } from "nest-winston";
import { ClsService } from "nestjs-cls";

import { AppModule } from "./app.module";
import { CONFIG_KEY, CorsConfigType, SessionConfigType } from "./core/config/configurations";
import { CUREENT_ENV, IS_PROD_ENV, RuntimeEnv } from "./core/constant/global-enum";
import { GlobalException } from "./core/exception/GlobalException";
import {
    combinedTransport,
    consoleTransport,
    createErrorTransport,
    exceptionTransport,
    rejectionTransport
} from "./core/logger/winston.transport";

async function bootstrap() {
    const app = await NestFactory.create<NestExpressApplication>(AppModule, {
        // logger: ["fatal", "error", "warn", "log", "debug", "verbose"] // nest 默认的日志系统，级别从高到低
        // logger: false // 如果你想完全禁用 NestJS 的日志系统，可以将 logger 选项设置为 false
    });

    const clsService = app.get(ClsService);

    // 这种方式启用 winston 日志系统，是在 nest 启动后，才替换掉 nest 默认的日志系统，因此可能不会接管或者接管部分 nest 的启动日志。
    app.useLogger(
        WinstonModule.createLogger({
            // 使用 nest-winston 日志系统，完全替代 nest 默认的日志系统
            // 选项（与 WinstonModule.forRoot() 相同）
            level: IS_PROD_ENV ? "log" : "debug",
            transports: IS_PROD_ENV
                ? [createErrorTransport(clsService), combinedTransport]
                : [consoleTransport, createErrorTransport(clsService)],
            exceptionHandlers: [exceptionTransport],
            rejectionHandlers: [rejectionTransport]
        })
    );

    /* 设置 HTTP 标头来帮助保护应用免受一些众所周知的 Web 漏洞的影响 */
    app.use(
        helmet({
            // contentSecurityPolicy: false, // 禁用 CSP，取消https强制转换
            crossOriginResourcePolicy: { policy: "cross-origin" }
        })
    );

    // 去掉响应头：x-powered-by: Express
    app.disable("x-powered-by");

    const configService = app.get(ConfigService);
    const { port, apiPrefix, apiDefaultGlobalVersion, appServerUrlPrefix } = configService.get(CONFIG_KEY.server);
    const projectConfig = configService.get(CONFIG_KEY.project);
    const corsConfig = configService.get<CorsConfigType>(CONFIG_KEY.cors);
    const swaggerConfig = configService.get(CONFIG_KEY.swagger);
    const sessionConfig = configService.get<SessionConfigType>(CONFIG_KEY.session);

    // 启用全局前缀
    app.setGlobalPrefix(apiPrefix);
    // 启用版本控制: url 前缀模式，全局模版版本：v1
    app.enableVersioning({
        type: VersioningType.URI,
        defaultVersion: apiDefaultGlobalVersion
    });

    // 启用全局异常过滤器
    app.useGlobalFilters(new GlobalException());
    // 启用全局验证管道
    app.useGlobalPipes(
        new ValidationPipe({
            disableErrorMessages: process.env.NODE_ENV === RuntimeEnv.PROD,
            whitelist: true
        })
    );

    // 启用 swagger
    if (swaggerConfig.enabled) {
        const config = new DocumentBuilder()
            .setTitle(projectConfig.name)
            .setDescription(projectConfig.description)
            .setVersion(projectConfig.version)
            .addBearerAuth()
            .setContact(projectConfig.author.name, projectConfig.author.url, projectConfig.author.email)
            .build();

        const swaggerDocumentOptions: SwaggerDocumentOptions = {};
        const document = SwaggerModule.createDocument(app, config, swaggerDocumentOptions);

        const swaggerCustomOptions: SwaggerCustomOptions = {
            jsonDocumentUrl: `${appServerUrlPrefix}/${swaggerConfig.jsonPath}`,
            yamlDocumentUrl: `${appServerUrlPrefix}/${swaggerConfig.yamlPath}`
        };
        SwaggerModule.setup(`${appServerUrlPrefix}/${swaggerConfig.path}`, app, document, swaggerCustomOptions);
    }
    if (sessionConfig.enabled) {
        app.use(session(sessionConfig.sessionOptions));
    }
    // 启用 cors
    if (corsConfig.enabled) {
        app.enableCors(corsConfig.corsOptons);
    }

    await app.listen(port, async (): Promise<void> => {
        const appServer = `http://localhost:${port}/${appServerUrlPrefix}`;

        if (swaggerConfig.enabled) {
            Logger.log(`📚 Swagger UI is running on: ${appServer}/${swaggerConfig.path}`, `${projectConfig.name}`);
            Logger.log(
                `🍚 Swagger json is running on: ${appServer}/${swaggerConfig.jsonPath}`,
                `${projectConfig.name}`
            );
            Logger.log(
                `🍜 Swagger yaml is running on: ${appServer}/${swaggerConfig.yamlPath}`,
                `${projectConfig.name}`
            );
        }
        Logger.log(`🐶 Runtime environment: ${CUREENT_ENV}`, `${projectConfig.name}`);
        Logger.log(`🚀 Application is running on: ${appServer}`, `${projectConfig.name}`);
        if (IS_PROD_ENV) {
            console.log(`🐶 Runtime environment: ${CUREENT_ENV}`);
            console.log(`🚀 Application is running on: ${appServer}`);
        }
    });
}
bootstrap();
